#include <jni.h>
#include <string>

#include "thread.h"
#include "rlog.h"
#include "rlog_impl.h"

static JavaVM* gVM = nullptr;

class JNIEnvWrap : public std::enable_shared_from_this<JNIEnvWrap> {
public:
    JNIEnvWrap() : env {nullptr}, need_detach {false} {
        if (gVM->GetEnv((void **)(&env), JNI_VERSION_1_4) == JNI_EDETACHED) {
            gVM->AttachCurrentThread(&env, nullptr);
            need_detach = true;
            //LOG_INFO() << "___ATTATCJH";
        }
    }
    ~JNIEnvWrap() {
        if (need_detach) {
            gVM->DetachCurrentThread();
        }
    }

    JNIEnv* operator->() {
        return env;
    }

    JNIEnv* Get() {
        return env;
    }

private:

    explicit JNIEnvWrap(const JNIEnvWrap&) = delete;
    JNIEnvWrap& operator=(const JNIEnvWrap&) = delete;

    JNIEnv* env;
    bool need_detach;
};

typedef std::shared_ptr<JNIEnvWrap> JNIEnvPtr;

JNIEnvPtr makeJNIEnvPtr()
{
    return std::make_shared<JNIEnvWrap>();
}


jobject log_java_callback;
jclass java_callback_clazz;
jmethodID callback_mid;

void initJavaEnv(JNIEnvPtr env)
{
    java_callback_clazz = static_cast<jclass>(env->Get()->NewGlobalRef(env->Get()->FindClass("com/s/remotelog/rpc/INativeLogCallback")));
    callback_mid = env->Get()->GetMethodID(java_callback_clazz, "onNativeLog", "(ILjava/lang/String;Ljava/lang/String;)V");
}

static void StartUploadThread()
{
    auto thread_ = std::make_shared<common::Thread>("log_thread");
    thread_->Poll();
    RLogImpl::Self()->AttachLoggingThread(thread_);
    RLogImpl::Self()->RegisterTaskCallback([](int level, const std::string& tag, const std::string& msg) {
        auto env = makeJNIEnvPtr();
        auto tag_java = env->Get()->NewStringUTF(tag.c_str());
        auto msg_java = env->Get()->NewStringUTF(msg.c_str());
        env->Get()->CallVoidMethod(log_java_callback, callback_mid, level, tag_java, msg_java);
    });
}

extern "C"
JNIEXPORT void JNICALL
Java_com_s_remotelog_rpc_NativeLogTransfer_registerNativeLogCallback(JNIEnv *env, jobject thiz,
                                                                     jobject callback)
{
    log_java_callback = env->NewGlobalRef(callback);
    auto jni_env_ptr = makeJNIEnvPtr();
    assert(jni_env_ptr != nullptr && jni_env_ptr->Get() != nullptr);
    initJavaEnv(jni_env_ptr);

    StartUploadThread();

    // test ...
    {
        for (int i = 0; i < 5; ++i) {
            RLog::i("RLog", "Just it : " + std::to_string(i));
        }
    }
}

jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    gVM = vm;
    return JNI_VERSION_1_4;
}

